#!/usr/bin/python3
# coding:utf-8
# Copyright 2022, JAKA
# All rights reserved.
"""
独立的UDP广播服务直接以操作系统服务的形式启动
"""

import os
import socket
from time import sleep
import configparser
import netifaces
import jkzuc
import json

USER_CFG_FILE = jkzuc.jkroot_dir + "/configs/JAKA/usersettings.ini"
BROADCAST_PORT = 54545
GRPC_PORT = 50051
INVALID_ROBID = "SysError"

E_PYITF_INVALID = -100  # zucmodule的jkzuc接口无效
E_JKZUC_INVALID = -101  # jkzuc服务未激活
E_TASK_INVALID = -102  # task未激活
E_GRPCSVR_CONFLICT = -201  # 有多个同端口的grpc服务
E_GRPCSVR_CLOSED = -202  # grpc服务自己关闭了
E_ROBID_INVALID = -300  # robotid错误


class MyConfigParser(configparser.ConfigParser):

    def __init__(self, defaults=None):
        configparser.ConfigParser.__init__(self, defaults=defaults)

    def optionxform(self, optionstr):
        return optionstr


def findInterface():
    interfaces = []
    # lo=[]
    # lo.append('lo')
    # lo.append('127.0.0.1')
    # lo.append('00-00-00-00-00-00')
    # lo.append('127.0.0.1')
    # interfaces.append(lo)
    for interface in netifaces.interfaces():
        info = []
        # 过滤掉不合法的网口
        if (
            (not interface.startswith("enp"))
            and (not interface.startswith("ens"))
            and (not interface.startswith("enx"))
            and (not interface.startswith("wlp"))
            and (not interface.startswith("eth"))
        ):
            continue

        try:
            mac = netifaces.ifaddresses(interface)[netifaces.AF_LINK][0]["addr"]
            mac = mac.replace(":", "-")  # 与 1.5.13 和 1.7.0 保持一致，使用‘-’ 分割MAC地址的字段，避免APP等终端解析时出现异常或兼容性问题。
            brdIp = netifaces.ifaddresses(interface)[netifaces.AF_INET][0]["broadcast"]
            ip = netifaces.ifaddresses(interface)[netifaces.AF_INET][0]["addr"]
            info.append(interface.encode("utf-8"))
            info.append(ip.encode("utf-8"))
            info.append(mac.encode("utf-8"))
            info.append(brdIp.encode("utf-8"))
            interfaces.append(info)
        except:
            # 有些网口因禁用关闭等原因导致获取时报出异常，对此直接跳过
            pass

    return interfaces


def checkPortState(port):
    stat = E_GRPCSVR_CLOSED
    info = os.popen("netstat -anut |grep %d | awk '{print $6}'" % (port)).read().strip()  # 提取端口状态
    for s in info.splitlines():
        if s.strip() == "LISTEN":
            if stat != 1:
                stat = 1
            else:  # 一个端口有两个LISTEN，在我们控制器中是不允许的,这种情况常见于jkzuc二次启动的时候，没有相关进程没有停掉
                stat = E_GRPCSVR_CONFLICT
                # print("[BRD]REPEAT 50051 LISTEN ERR")
                break
        # elif s.strip() == 'TIME_WAIT':
        #     '''
        #     我方主动断开连接,收到对方确认后状态变为TIME_WAIT。
        #     处于TIME_WAIT状态的连接占用的资源不会被内核释放.
        #     这种情况说明我们自己把端口断了
        #     '''
        #     stat = E_GRPCSVR_CLOSED
        #     # print("[BRD]REPEAT 50051 TIME_WAIT ERR")
        #     break
    return stat


def checkUdpPortValid():
    pid_and_name = os.popen("netstat -anup|grep python|awk '{print $NF}'").read().strip().splitlines()
    for item in pid_and_name:
        pid = item.split("/")
        if len(pid) > 0 and pid[0].isdigit():
            path = os.popen("ps -aux|grep %s|grep -v grep" % (pid[0])).read()
            if "jaka_server" in path:
                return False
    return True


def checkJKZUC(jkzucModule):
    try:
        s = jkzucModule.stat()
        s.poll()
    except:
        # print("[BRD]S.POLL() ERR")
        return E_PYITF_INVALID
    # if os.popen("ps -A|grep jkzuc$|awk '{print $1}'").read().strip() == "":
    #     # print("error jkzuc")
    #     return E_JKZUC_INVALID
    if os.popen("ps -A|grep zuctaskmod$|awk '{print $1}'").read().strip() == "":
        # print("error zuctaskmod")
        return E_TASK_INVALID
    return checkPortState(GRPC_PORT)


def robotIdFormat(serial_num):
    prefix = ""
    content = str(serial_num)
    serial = serial_num // 100000000
    ver_id = serial_num % 100000000
    if serial == 0:
        prefix = "Zu"
        robotModel = (serial_num % 100000000) / 10000
        if robotModel == 313 or robotModel == 510 or robotModel == 511 or robotModel == 729 or robotModel == 730 or robotModel == 1212 or robotModel == 1812:
            prefix = "C"
        elif robotModel == 515 or robotModel == 731 or robotModel == 1213 or robotModel == 1610 or robotModel == 1813:
            prefix = "Pro"
        content = str(ver_id)
    elif serial == 1:
        prefix = "MC"
        content = "%d" % ver_id
    elif serial == 3:
        prefix = "MAX"
        content = "%d" % ver_id
    elif serial == 2:
        robotModel = (serial_num % 100000000) / 10000
        prefix = "AT"  # 阿童木
        if robotModel == 20710:
            prefix = "AT"
        elif robotModel == 20310 or robotModel == 20311 or robotModel == 22001 or robotModel == 22002:
            prefix = "DU"
        content = "%d" % ver_id
    return prefix + content


def getRobotInfo(jkzucModule, cfgfile):
    ret = {"robId": INVALID_ROBID, "interpStat": 0, "name": "JAKAROB", "connectStat": True, "ver": "0.0.0_0", "errcode": 0}
    err = 0
    try:
        err = checkJKZUC(jkzucModule)
        ret["errcode"] = err
        if err < 0:
            raise Exception("invalid jkzuc")

        s = jkzucModule.stat()
        s.poll()
        if s.robot_serial_num == 0:
            err = E_ROBID_INVALID
            ret["errcode"] = err
            raise Exception("invalid robotid")  # 仅会出现在jkzuc接口刚刚启动的时候

        ret["name"] = s.robot_name
        ret["ver"] = jkzucModule.controller_version
        ret["robId"] = str(robotIdFormat(s.robot_serial_num))
        ret["interpStat"] = s.interp_state
        if s.app_connection_status == 1:
            ret["connectStat"] = True
        else:
            ret["connectStat"] = False
    except:
        # sleep(2)
        # print traceback.format_exc()
        try:
            ret["ver"] = jkzucModule.controller_version
        except:
            pass
        ret["robId"] = INVALID_ROBID + str(err)
        ret["connectStat"] = True  # 异常情况下标记为占用状态，以防止旧的APP能够让用户连接
        try:
            parser = MyConfigParser()
            parser.read(cfgfile, encoding="utf-8")
            ret["name"] = parser.get("ROBOT", "NAME", fallback="NULL")
        except:
            ret["name"] = "NULL"
            pass
    return ret


def robotInfoFormat(robInfo, mac):
    ret = ""
    ret = ret + robInfo["robId"] + ";"
    ret = ret + str(robInfo["interpStat"]) + ";"  # APP程序分享功能会用到
    ret = ret + str(robInfo["connectStat"]) + ";"
    ret = ret + robInfo["name"] + ";"
    ret = ret + robInfo["ver"] + ";"
    ret = ret + str(mac) + ";"
    ret = ret + json.dumps({"sys_state": robInfo["errcode"]})
    return ret


def broadcase(brcst, port, interfaces, robInfo):
    ret = 0
    if len(interfaces) == 0:
        return
    for itf in interfaces:
        brd = itf[3]
        ret = ret + brcst.sendto(robInfo.encode(), (brd, port))
        sleep(0.1)
    return ret


if __name__ == "__main__":
    brcst = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    brcst.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    brcst.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    deley_timer_cnt = 10
    while True:
        sleep(1)
        # 若已经有相同的udp端口则不做任何事件，以避免老版本手动升级造成的brd无法退出，而产生两个广播的问题
        if checkUdpPortValid():
            ifs = findInterface()
            rob = getRobotInfo(jkzuc, USER_CFG_FILE)

            # 前10s若检测出异常不上报
            if rob["robId"] == INVALID_ROBID and deley_timer_cnt > 0:
                deley_timer_cnt = deley_timer_cnt - 1
                continue

            info = robotInfoFormat(rob, ifs[0][2])
            sendSize = broadcase(brcst, BROADCAST_PORT, ifs, info)
        else:
            # brd为后台服务会自己重启，这里退出是为了重新加载jkzuc模块，以保证udp显示的数据为最新的
            exit(0)
